<?php
/** JavaScript-type object class, or Perl-type %hash
*   Allows both object-type and array-type access: $a->var and $a['var']
*   Index may be of number or string type (value of Yz_Int, Yz_String)
*
*   The array value of Yz_Hash can be fetched with:
*   - toArray()
*   The keys:
*   - keys()
*   The values (without keys):
*   - values()
*/

class Yz_Hash
    extends Yz_Array_Abstract
    implements Iterator,ArrayAccess,Countable,Yz_Array_Interface
{


    /******************************
    ***     Public methods      ***
    *******************************/

    /** Get the defined keys
     *  @return array
     */
    public function keys()
    {
        return array_keys($this->__dict__);
    } // keys

    /** Get the defined values
    *   @return array<index:value>
    */
    public function values()
    {
        return array_values($this->__dict__);
    } // values


    /** get a hash containing some of the elements of the original hash
     *
     *  @param int $from
     *  @param int|null $count
     *  @return Yz_Hash
     *  @todo check param types
     */
    public function slice( $from, $count=null )
    {
        debug();
        return $this->_slice($from, $count, true);
    } // slice



    /** get an array containing all of the elements of the input arrays
     *
     *  @param Yz_Array_Interface $input
     *  @param bool(false) $overwrite whether to preserve old values (false) or replace them with the new ones (true)
     *  @return self/Yz_Array_Abstract
     *  @todo check param types
     */
    public function unionRecursive( $input, $overwrite=false )
    {
        debug();

        // ensure that the input is a native PHP array
        $input = $this::valueOf($input);
        if (null === $input)
            throw TypeError::_new_();
        if (! $input)
            return $this;

        return $this->_unionRecursive($input, $overwrite);
    }

    /********************************
     *      Protected methods       *
     ********************************/

    /** validate the index, disallow non-string and non-number types
     *      force index type to be string
     *   @param mixed index
     *   @return mixed valid index
     *   @throw TypeError if index type not valid
     *   @throw AttributeError if index value not valid
     */
    protected function adjustIndex( $index )
    {
        if (! is_scalar($index) or is_bool($index))
            throw TypeError::_new_();
        if ("" === $index)
            throw ValueError::_new_();
        return (string)$index;
    }

    /** the default getter is called after index verification
     *      if index not found, return null instead of throwing InaccessibleProperty exception
     *  @param string index
     *  @return mixed|null
     */
    protected function _get_( $index )
    {
        if (! $this->_has_( $index ))
            return null;

        return $this->__dict__[$index];
    }

    /** the default setter is called after index verification
     *  @param string index
     *  @return chainable
     */
    protected function _set_( $index, $value )
    {
        $this->__dict__[$index] = $value;
        return $this;
    }

    /** the default unsetter is called after index verification
     *  @param string index
     *  @return chainable
     */
    protected function _del_( $index )
    {
        unset($this->__dict__[$index]);
        return $this;
    }

    /**
     *
     */
    protected function _unionRecursive(array $input, $overwrite=false)
    {
        //  iterate over the original keys/values
        //  recursively union the values under keys which exist in $input,
        //  and store back into $input
        //
        foreach ($this->__dict__ as $index => $origvalue) {

            // by default, set out-value to original value
            $out = $origvalue;
            if (array_key_exists($index, $input)) {
                if ($overwrite) {   // swap
                    $in = $out;
                    $out = $input[$index];
                } else {
                    $in = $input[$index];
                }
                $invalue = $this::valueOf($in);

                if (null === $invalue) {
                    // if input not an array-type, preserve the original value
                } else {
                    if ($out instanceof Yz_Hash) { // recurse into Yz_Hash
                        $out = $out->_unionRecursive($invalue);
                    } else if ($out instanceof Yz_Array) { // append to Yz_Array
                        $out = $out->append($invalue);
                    } else if (is_array($out)) {
                        // for native-array original value, we have to do unionRecursive
                        // using an instance of Yz_Hash
                        // and preserving the type
                        $out = $this::fromArray($out)
                            ->_unionRecursive($invalue)
                            ->toArray();
                    } else {
                        // if original value not an array-type, preserve it, too
                    }
                }
            }

            // set the new value
            $input[$index] = $out;
        }

        // now that there are only new keys in input, we can safely append them
        return new $this($input);
    }


} // class

